package com.zhuangjie.redis.service.impl;

import com.zhaungjie.base.utils.StringUtils;
import com.zhuangjie.redis.service.PhoneCodeRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
public class PhoneCodeRedisServiceImpl implements PhoneCodeRedisService {
    private final String KEY_PREFIX = "phoneCode::";
    /**
     * 30 timeUnit单位到期
     */
    private final int EXPIRATION = 30;
    /**
     * 单位：分钟
     */
    private final TimeUnit timeUnit = TimeUnit.MINUTES;
    /**
     * 允许的最大错误次数
     */
    private final int MAX_ERROR_COUNT = 3;
    /**
     * 默认手机号地区前缀
     */
    private final String  DEFAULT_PHONE_AREA_CODE = "__noPhoneAreaCode__";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 兼容的场景
     *
     * @param scene 场景
     * @return {@link String}
     */
    private String compatibleScene(String scene) {
        return StringUtils.isBlank(scene)?"__defaultScene__":scene;
    }

    /**
     * 兼容的场景
     *
     * @param scene 场景
     * @return {@link String}
     */
    private String compatiblePhoneAreaCode(String scene) {
        return StringUtils.isBlank(scene)?"__defaultPhoneAreaCode__":scene;
    }
    /**
     * 完整电话号码
     *
     * @param phoneAreaCode 电话区号
     * @param phone         电话
     * @return {@link String}
     */
    private String fullPhoneNumber(String phoneAreaCode, String phone) {
        return compatiblePhoneAreaCode(phoneAreaCode) + "::" + phone;
    }


    @Override
    public void checkCode(String scene, String phoneAreaCode, String phone, String code) throws Exception {
        if (phone == null || code == null) {
            throw new Exception("校验失败，手机号或验证码不能为空");
        }
        String key = KEY_PREFIX + compatibleScene(scene) + "::" + fullPhoneNumber(phoneAreaCode,phone);
        // 验证手机验证码(使用lua脚本获s取验证码且能获取删除key，获取不到返回null)
//        Object codeInRedis = redisTemplate.opsForValue().get(key);
        String luaScript = "local code = redis.call('get',KEYS[1])\n"+
                "if(code == nil) then\n"+
                    "return nil\n"+
                "elseif(code == ARGV[1]) then\n"+
                    "redis.call('del',KEYS[1])\n"+
                    "return 1\n"+
                "else\n"+
                    "return 0\n"+
                "end\n";
        Object redisResult =  redisTemplate.execute(new DefaultRedisScript(luaScript,Long.class), Arrays.asList(key),code);
        if (redisResult == null) {
            throw new RuntimeException("验证码失效或不存在，请重新获取！");
        } else if (Objects.equals(redisResult,0L)) { // codeInRedis从redis中获取是整数
            // 输入的错误，记录，当超时指定次数后，删除该code，让用户重新获取
            handleErrorCodeInput(key);
            throw new RuntimeException("验证码不正确");
        }
    }



    /**
     * 输入代码错误操作后
     *
     * @param key 钥匙
     */
    private void handleErrorCodeInput(String key) {

        // 错误的key
        String errorKey =toErrorKeyByKey(key);;
        // lua
        String luaScript = "local errorCount = (tonumber(redis.call('GET', KEYS[2])) or 0)+1 "+
                "redis.call('set', KEYS[2],errorCount, 'EX', ARGV[1]) "+
                "if (errorCount > tonumber(ARGV[2])) then "+
                    "redis.call('DEL', KEYS[1]) "+
                "end ";
        // exec lua
        redisTemplate.execute(new DefaultRedisScript(luaScript), Arrays.asList(key,errorKey), TimeUnit.MINUTES.toSeconds(EXPIRATION),Long.valueOf(MAX_ERROR_COUNT));
    }

    private String toErrorKeyByKey(String key) {
        return key+"::error_count";
    }

    @Override
    public void checkCode(String scene, String phone, String code) throws Exception {
        checkCode(scene, DEFAULT_PHONE_AREA_CODE, phone, code);
    }

    @Override
    public void submitCode(String scene, String phoneAreaCode, String phone, String code) {
        if (phone == null || code == null) {
            throw new RuntimeException("校验失败，手机号或验证码不能为空");
        }
        String codeKey = KEY_PREFIX + compatibleScene(scene) + "::" +  fullPhoneNumber(phoneAreaCode,phone);
        String errorKey = toErrorKeyByKey(codeKey);
        String lua = "redis.call('DEL',KEYS[2]) "+
                "redis.call('SET',KEYS[1], ARGV[1], 'EX', ARGV[2]) ";
        redisTemplate.execute(new DefaultRedisScript(lua), Arrays.asList(codeKey,errorKey),code,TimeUnit.MINUTES.toSeconds(EXPIRATION));
    }
    /**
     * 提交验证码
     *
     * @param phone 电话
     * @param code  密码
     */
    @Override
    public void submitCode(String scene, String phone, String code) {
        submitCode(scene, DEFAULT_PHONE_AREA_CODE,phone, code);
    }

}
